using System.Collections.Concurrent;
using Crux.Core.StackExchangeRedis;
using Microsoft.Extensions.Logging;
using Nito.AsyncEx;

namespace Crux.Core.Cache.Extensions;

/// <summary>
/// LockProvider
/// </summary>
public class LockProvider(
    IRedisOperator redisOperator,
    IHashProvider hashProvider,
    ILogger<LockProvider> logger)
    : ILockProvider, IDisposableObservable
{
    #region static properties

    #endregion

    #region private fields
    private static ConcurrentDictionary<ulong, LockInfo> _localState = new ConcurrentDictionary<ulong, LockInfo>();
    private readonly ConcurrentDictionary<string, LockInfo> _scopeState = new ConcurrentDictionary<string, LockInfo>();
    #endregion private fields

    #region implements ILockProvider

    /// <inheritdoc/>
    public bool LockTake(string cacheKey)
    {
        return AsyncContext.Run(() => LockTakeAsync(cacheKey));
    }

    /// <inheritdoc/>
    public async Task<bool> LockTakeAsync(string cacheKey)
    {
        var hashKey = hashProvider.ComputeHash($"{cacheKey}");

        var state = LockInfo.CreateState();

        if (!_localState.TryAdd(hashKey, state))
        {
            if (_localState.TryGetValue(hashKey, out var prevState) &&
                prevState.IsAutoExpiry())
            {
                _localState.TryRemove(hashKey, out _);
                _scopeState.TryRemove(cacheKey, out _);
            }

            if (!_localState.TryAdd(hashKey, state))
            {
                if (logger.IsEnabled(LogLevel.Trace))
                {
                    logger.LogTrace(
                        $"获取本地锁失败, cacheKey:{cacheKey},prevLockTime:{prevState?.LockTime:yyyy-MM-dd HH:mm:ss.fff},IsAutoExpiry:{prevState?.IsAutoExpiry()},threadId:{Thread.CurrentThread.ManagedThreadId}");
                }

                return false;
            }
        }

        if (logger.IsEnabled(LogLevel.Trace))
        {
            logger.LogTrace(
                $"获取本地锁成功,cacheKey:{cacheKey},lockTime:{state.LockTime:yyyy-MM-dd HH:mm:ss.fff},expriy:{state.Expiry},threadId:{Thread.CurrentThread.ManagedThreadId}");
        }

        var result = await redisOperator.LockTakeAsync($"Lock:{hashKey}", state.LockValue, state.Expiry).ConfigureAwait(false);

        state.Register(async () =>
        {
            await redisOperator.LockReleaseAsync($"Lock:{hashKey}", state.LockValue).ConfigureAwait(false);
        });

        if (logger.IsEnabled(LogLevel.Trace))
        {
            logger.LogTrace($"获取分布式锁情况,result:{result},threadId:{Thread.CurrentThread.ManagedThreadId}");
        }

        if (!result)
        {
            _localState.TryRemove(hashKey, out _);
        }
        else
        {
            _scopeState.TryAdd(cacheKey, state);
        }

        return result;
    }

    /// <inheritdoc/>
    public bool LockRelease(string cacheKey)
    {
        return AsyncContext.Run(() => LockReleaseAsync(cacheKey));
    }

    /// <inheritdoc/>
    public async Task<bool> LockReleaseAsync(string cacheKey)
    {
        var hashKey = hashProvider.ComputeHash($"{cacheKey}");

        if (!_localState.TryRemove(hashKey, out var state))
        {
            if (logger.IsEnabled(LogLevel.Trace))
            {
                logger.LogTrace($"本地锁不存在，假设分布式已经释放成功了");
            }

            return false;
        }

        _scopeState.TryRemove(cacheKey, out _);

        if (logger.IsEnabled(LogLevel.Trace))
        {
            logger.LogTrace("本地锁未释放，执行释放");
        }

        try
        {
            if (state.IsAutoExpiry())
            {
                return true;
            }

            if (logger.IsEnabled(LogLevel.Trace))
            {
                logger.LogTrace("分布式锁未自动过期，执行释放");
            }

            state.Cancel();

            return await redisOperator.LockReleaseAsync($"Lock:{hashKey}", $"{state.LockTime:yyyy-MM-dd HH:mm:ss.fff}").ConfigureAwait(false);
        }
        catch (Exception e)
        {
            logger.LogError(e, e.Message);

            return true;
        }
    }

    /// <inheritdoc/>
    public bool IsDisposed { get; set; }

    /// <inheritdoc/>
    public void Dispose()
    {
        if (IsDisposed) return;

        IsDisposed = true;

        foreach (var state in _scopeState)
        {
            var key = state.Key;

            LockRelease(key);
        }

        _scopeState.Clear();
    }

    #endregion implements ILockProvider
}